﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;



namespace DARE
{
    [AttrTypeDependentSerializable(typeof(DARE.Save.DataStructures.CEntityData))]
    public class CEntity : ARenderable
    {

    #region fields

        private APhysicObject m_physicObject;
        private AModel m_model;
        private Dictionary<string, CSprite3D> m_sprite3d;
#if !WINDOWS_PHONE
        private Dictionary<string, CSound> m_sounds;
#endif
        private Dictionary<string, AEmitter> m_emitter;

    #endregion

    #region properties

        public AModel Model
        {
            get { return m_model; }
            set
            {
                if (!value.Equals(m_model))
                {
                    DetachModel();
                    AttachModel(value);
                }
            }
        }

        public APhysicObject Physic
        {
            get { return m_physicObject; }
            // should disapear
            set
            {
                if (!value.Equals(m_physicObject))
                    m_physicObject = value;
            }
        }

        public Dictionary<string, CSprite3D> Sprite3D
        {
            get { return m_sprite3d; }
            set
            {
                DetachSprite3D();
                foreach (CSprite3D sprite in value.Values)
                    AttachSprite3D(sprite);
            }
        }

#if !WINDOWS_PHONE
        public Dictionary<string, CSound> Sound
        {
            get { return m_sounds; }
            set
            {
                DetachSound();
                AttachSound(value);
            }
        }
#endif
        public Dictionary<string, AEmitter> Emitter
        {
            get { return m_emitter; }
            set
            {
                DetachEmitter();
                foreach (AEmitter emitter in value.Values)
                    AttachEmitter(emitter);
            }
        }

    #endregion

    #region ctor

        public CEntity()
        {
            Initialize(null);
        }

        public CEntity(CNode node)
        {
            Initialize(node);
        }

        private void Initialize(CNode node)
        {
            m_physicObject = null;
            m_model = null;
            m_sprite3d = new Dictionary<string, CSprite3D>();
#if !WINDOWS_PHONE
            m_sounds = new Dictionary<string, CSound>();
#endif
            m_emitter = new Dictionary<string, AEmitter>();
            m_entity = this;
            if (node != null)
                node.AttachEntity(this);
        }

        //public CEntity Clone()
        //{
        //    CEntity cloneEntity = new CEntity();

        //    if (m_physicObject != null)
        //        cloneEntity.AttachPhysic(m_physicObject.Clone());
        //    if (m_model != null)
        //        cloneEntity.AttachModel(m_model.Clone());

        //    return cloneEntity;
        //}

        //public CEntity(SerializationInfo info, StreamingContext ctxt)
        //{
        //    m_physicObject = (APhysicObject)info.GetValue("physicobject", typeof(APhysicObject));
        //    m_model = (AModel)info.GetValue("model", typeof(AModel));
        //    m_sprite3d = (Dictionary<string, CSprite3D>)info.GetValue("sprite3d", typeof(Dictionary<string, CSprite3D>));
        //    m_sounds = (Dictionary<string, CSound>)info.GetValue("sounds", typeof(Dictionary<string, CSound>));
        //    m_emitter = (Dictionary<string, AEmitter>)info.GetValue("emitter", typeof(Dictionary<string, AEmitter>));
        //}

        //public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        //{
        //    info.AddValue("physicobject", m_physicObject);
        //    info.AddValue("model", m_model);
        //    info.AddValue("sprite3d", m_sprite3d);
        //    info.AddValue("sounds", m_sounds);
        //    info.AddValue("emitter", m_emitter);
        //}

        //public CEntity(string name)
        //{
        //    m_entity = this;
        //    m_name = name;
        //}

        //public CEntity(CNode node, string name)
        //{
        //    node.AttachEntity(this);
        //    m_name = name;
        //}

    #endregion

    #region run

        public override void Update(GameTime gameTime)
        {
            if (m_physicObject != null)
                m_physicObject.Update(gameTime);
#if !WINDOWS_PHONE
            foreach (CSound sound in m_sounds.Values)
                if (sound.Updatable)
                    sound.Update(gameTime);
#endif
            foreach (AEmitter emitter in m_emitter.Values)
                if (emitter.Updatable)
                    emitter.Update(gameTime);
            if (m_model != null)
                m_model.Update(gameTime);
            foreach (CSprite3D sprite in m_sprite3d.Values)
                if (sprite.Updatable)
                    sprite.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            CRender3D render = Node.Render3D;

            if (m_physicObject != null)
                m_physicObject.Draw(gameTime);
            foreach (AEmitter emitter in m_emitter.Values)
                if (emitter.Drawable)
                    emitter.Draw(gameTime);
            if (m_model != null)
                m_model.Draw(gameTime);
            // add the sprite to the render to order them
            if (render != null)
            {
                foreach (CSprite3D sprite in m_sprite3d.Values)
                    if (sprite.Drawable)
                        render.DrawSprite(sprite);
            }
            // else draw them normaly
            else
            {
                foreach (CSprite3D sprite in m_sprite3d.Values)
                    if (sprite.Drawable)
                        sprite.Draw(gameTime);
            }
        }

    #endregion

    #region get

        public CSprite3D GetSprite3D(string name)
        {
            if (m_sprite3d.ContainsKey(name))
                return m_sprite3d[name];
            return null;
        }

#if !WINDOWS_PHONE
        public CSound GetSound(string name)
        {
            if (m_sounds.ContainsKey(name))
                return m_sounds[name];
            return null;
        }
#endif
        public AEmitter GetEmitter(string name)
        {
            if (m_emitter.ContainsKey(name))
                return m_emitter[name];
            return null;
        }

    #endregion

    #region methods

#if !WINDOWS_PHONE
        public CSound PlaySound(string name)
        {
            CSound sound = GetSound(name);

            if (sound != null)
                sound.Play();
            return sound;
        }
#endif
    #endregion

    #region attach/detach

        public CEntity Attach(object obj)
        {
            if (obj is APhysicObject)
                return AttachPhysic(obj as APhysicObject);
            else if (obj is AModel)
                return AttachModel(obj as AModel);
            else if (obj is CSprite3D)
                return AttachSprite3D(obj as CSprite3D);
#if !WINDOWS_PHONE
            else if (obj is CSound)
                return AttachSound(obj as CSound);
#endif
            else if (obj is AEmitter)
                return AttachEmitter(obj as AEmitter);
            throw new Exception("Unknown obj to attach !");
        }

        public CEntity Detach(object obj)
        {
            if (obj is APhysicObject)
                return DetachPhysic();
            else if (obj is AModel)
                return DetachModel();
            else if (obj is CSprite3D)
                return DetachSprite3D(obj as CSprite3D);
#if !WINDOWS_PHONE
            else if (obj is CSound)
                return DetachSound(obj as CSound);
#endif
            else if (obj is AEmitter)
                return DetachEmitter(obj as AEmitter);
            throw new Exception("Unknown obj to detach !");
        }

        public CEntity AttachToNode(CNode node)
        {
            if (node != null)
                node.AttachEntity(this);
            return this;
        }

        public CEntity DetachFromNode()
        {
            if (m_node != null)
                m_node.DetachEntity();
            return this;
        }

        public CEntity AttachPhysic(APhysicObject physic)
        {
            if (physic == null)
                return this;

            if (m_node == null)
                throw new Exception("Cannot attach physic on an object that don't have a node !");
            if (m_node.Render3D == null)
                throw new Exception("Cannot attach physic, no Render3D found !");

            if (m_physicObject != null)
                DetachPhysic();

            m_physicObject = physic;
            physic.Entity = this;
            physic.Node = m_node;
            if (physic.BasicModel != null)
                physic.BasicModel.Node = m_node;

            physic.Body.MoveTo(m_node.Position, Matrix.CreateFromQuaternion(m_node.Orientation));

            CPhysicTerrain terrain = null;
            if ((terrain = physic as CPhysicTerrain) != null)
            {
                terrain.TerrainInfo.OwnerNode = m_node;
                if (!m_node.Render3D.TerrainInfos.Contains(terrain.TerrainInfo))
                    m_node.Render3D.TerrainInfos.Add(terrain.TerrainInfo);

                m_node.IgnorePhysicsTransform = false;
            }

            if (m_node.Render3D.CollisionNode.ContainsKey(physic.Skin))
                m_node.Render3D.CollisionNode.Remove(physic.Skin);
            m_node.Render3D.CollisionNode.Add(physic.Skin, m_node);

            // current render 3d settings
            if (Physic.BasicModel != null)
            {
                Physic.BasicModel.Show = m_node.Render3D.ShowCollision;
                Physic.BasicModel.FillMode = m_node.Render3D.CollisionFillMode;
                Physic.BasicModel.CullMode = m_node.Render3D.CollisionFillMode == FillMode.WireFrame ? 
                    CullMode.None : CullMode.CullCounterClockwiseFace;
            }

            m_node.IgnorePhysicsTransform = false;
            return this;
        }

        public CEntity DetachPhysic()
        {
            if (m_physicObject == null)
                return this;

            if (m_node == null)
                throw new Exception("Cannot detach physic on an object that don't have a node !");
            if (m_node.Render3D == null)
                throw new Exception("Cannot detach physic, no Render3D found !");

            CPhysicTerrain terrain = null;
            if ((terrain = m_physicObject as CPhysicTerrain) != null)
            {
                terrain.TerrainInfo.OwnerNode = null;
                if (m_node.Render3D.TerrainInfos.Contains(terrain.TerrainInfo))
                    m_node.Render3D.TerrainInfos.Remove(terrain.TerrainInfo);
            }

            if (m_node.Render3D.CollisionNode.ContainsKey(m_physicObject.Skin))
                m_node.Render3D.CollisionNode.Remove(m_physicObject.Skin);

            m_node.Render3D.PhysicsSystem.RemoveBody(m_physicObject.Body);
            m_node.Render3D.PhysicsSystem.CollisionSystem.RemoveCollisionSkin(m_physicObject.Skin);

            m_physicObject.BasicModel.Node = null;
            m_physicObject.Node = null;
            m_physicObject.Entity = null;
            m_physicObject = null;
            return this;
        }

        public CEntity AttachModel(AModel model)
        {
            if (model != null)
            {
                m_model = model;
                model.Entity = this;
                model.Node = m_node;
                model.AttachToRenderer();
                if (m_node.Render3D != null)
                {
                    // current render 3d settings
                    if (model as CBasicModel != null)
                        ((CBasicModel)model).Slices = m_node.Render3D.BasicModelSlices;
                    model.FillMode = m_node.Render3D.FillMode;
                    model.CullMode = m_node.Render3D.FillMode == FillMode.WireFrame ? 
                        CullMode.None : CullMode.CullCounterClockwiseFace;
                }
            }
            return this;
        }

        public CEntity DetachModel()
        {
            if (m_model != null)
            {
                m_model.DetachFromRenderer();
                m_model.Node = null;
                m_model.Entity = null;
                m_model = null;
            }
            return this;
        }

        public CEntity AttachSprite3D(CSprite3D sprite)
        {
            if (!m_sprite3d.ContainsKey(sprite.Name))
            {
                sprite.Node = m_node;
                sprite.Entity = this;
                m_sprite3d.Add(sprite.Name, sprite);
            }
            return this;
        }

        public CEntity DetachSprite3D(CSprite3D sprite)
        {
            return DetachSprite3D(sprite.Name);
        }

        public CEntity DetachSprite3D(string name)
        {
            if (m_sprite3d.ContainsKey(name))
            {
                m_sprite3d[name].Node = null;
                m_sprite3d[name].Entity = null;
                m_sprite3d.Remove(name);
            }
            return this;
        }

        public CEntity DetachSprite3D()
        {
            foreach (CSprite3D sprite in m_sprite3d.Values)
            {
                sprite.Node = null;
                sprite.Entity = null;
            }
            m_sprite3d.Clear();
            return this;
        }

#if !WINDOWS_PHONE
        public CEntity AttachSound(Dictionary<string, CSound> soundList)
        {
            foreach (CSound sound in soundList.Values)
                AttachSound(sound);
            return this;
        }

        public CEntity AttachSound(CSound sound)
        {
            if (!m_sounds.ContainsKey(sound.Name))
            {
                sound.Node = m_node;
                sound.Entity = this;
                m_sounds.Add(sound.Name, sound);
            }
            else
                throw new Exception("Entity sound list already contain this sound");
            return this;
        }

        public CEntity AttachSound(string name, string soundName)
        {
            return AttachSound(new CSound(name, soundName));
        }

        public CEntity DetachSound(CSound sound)
        {
            return DetachSound(sound.Name);
        }

        public CEntity DetachSound(string name)
        {
            if (m_sounds.ContainsKey(name))
            {
                m_sounds[name].Node = null;
                m_sounds[name].Entity = null;
                CDare.Instance.AudioMgr.SoundMgr.RemoveSound(m_sounds[name]);
                m_sounds.Remove(name);
            }
            return this;
        }

        public CEntity DetachSound()
        {
            foreach (CSound sound in m_sounds.Values)
            {
                sound.Node = null;
                sound.Entity = null;
                CDare.Instance.AudioMgr.SoundMgr.RemoveSound(sound);
            }
            m_sounds.Clear();
            return this;
        }
#endif
        public CEntity AttachEmitter(AEmitter emitter)
        {
            if (!m_emitter.ContainsKey(emitter.Name))
            {
                emitter.Node = m_node;
                emitter.Entity = this;
                m_emitter.Add(emitter.Name, emitter);
                m_node.Order = EOrder.BACKTOFRONT;
                //emitter.Initialize();
            }
            return this;
        }

        public CEntity DetachEmitter(AEmitter emitter)
        {
            return DetachEmitter(emitter.Name);
        }

        public CEntity DetachEmitter(string name)
        {
            if (m_emitter.ContainsKey(name))
            {
                m_emitter[name].Node = null;
                m_emitter[name].Entity = null;
                m_emitter.Remove(name);
            }
            return this;
        }

        public CEntity DetachEmitter()
        {
            foreach (AEmitter emitter in m_emitter.Values)
            {
                emitter.Node = null;
                emitter.Entity = null;
            }
            m_emitter.Clear();
            return this;
        }

        public CEntity DetachAll()
        {
            DetachEmitter();
            DetachModel();
            DetachPhysic();
#if !WINDOWS_PHONE
            DetachSound();
#endif
            DetachSprite3D();
            return this;
        }

    #endregion

    #region createphysic

        #region physics lock

        // set the render active, attach the physics and restore its state
        // this is made because the physics objects get the current physicssystem wich 
        // is activated in the SetActiveRender method.
        // if we do not this, the physics object will be added to the active render3d 
        // physicssystem, but the visual object will be added to this render.

        private CRender3D m_renderSave = null;
        private bool m_physicsLock = false;

        private bool PhysicsLock
        {
            get { return m_physicsLock; }
            set
            {
                if (m_node == null || m_node.Render3D == null || m_node.Render3D.Scene == null)
                    throw new Exception("Cannot create physics if the node, render or scene is null");
                if (value != m_physicsLock)
                {
                    if (value) // lock
                    {
                        m_renderSave = m_node.Render3D.Scene.ActiveRender3D;
                        m_node.Render3D.Scene.SetActiveRender(m_node.Render3D);
                    }
                    else // unlock
                        m_node.Render3D.Scene.SetActiveRender(m_renderSave);
                    m_physicsLock = value;
                }
            }
        }

        #endregion

        public CEntity CreatePhysicPlane()
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicPlane());
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicPlane(float size)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicPlane(size));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicBox()
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicBox());
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicBox(float x, float y, float z)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicBox(x, y, z));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicBox(Vector3 size)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicBox(size));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicCylinder()
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicCylinder());
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicCylinder(float length, float diameter)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicCylinder(length, diameter));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicSphere()
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicSphere());
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicSphere(float diameter)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicSphere(diameter));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicCapsule()
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicCapsule());
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicCapsule(float length, float diameter)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicCapsule(length, diameter));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicHeightmap(CHeightMapInfo hInfo)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicHeightmap(hInfo, m_node.Position));
            PhysicsLock = false;
            return this;
        }

        public CEntity CreatePhysicTerrain(CTerrainInfo terrainInfo)
        {
            PhysicsLock = true;
            AttachPhysic(new CPhysicTerrain(terrainInfo, m_node.Position));
            PhysicsLock = false;
            return this;
        }

    #endregion

    #region basic model

        public CEntity CreateBasicPlane()
        {
            return AttachModel(new CPlaneModel(m_node.Name));
        }

        public CEntity CreateBasicPlane(float size)
        {
            return AttachModel(new CPlaneModel(m_node.Name, size));
        }

        public CEntity CreateBasicBox()
        {
            return AttachModel(new CBoxModel(m_node.Name));
        }

        public CEntity CreateBasicBox(float x, float y, float z)
        {
            return AttachModel(new CBoxModel(m_node.Name, x, y, z));
        }

        public CEntity CreateBasicBox(Vector3 size)
        {
            return AttachModel(new CBoxModel(m_node.Name, size.X, size.Y, size.Z));
        }

        public CEntity CreateBasicCylinder()
        {
            return AttachModel(new CCylinderModel(m_node.Name));
        }

        public CEntity CreateBasicCylinder(float length, float diameter)
        {
            return AttachModel(new CCylinderModel(m_node.Name, length, diameter));
        }

        public CEntity CreateBasicSphere()
        {
            return AttachModel(new CSphereModel(m_node.Name));
        }

        public CEntity CreateBasicSphere(float diameter)
        {
            return AttachModel(new CSphereModel(m_node.Name, diameter));
        }

        public CEntity CreateBasicCapsule()
        {
            return AttachModel(new CCapsuleModel(m_node.Name));
        }

        public CEntity CreateBasicCapsule(float length, float diameter)
        {
            return AttachModel(new CCapsuleModel(m_node.Name, length, diameter));
        }

        public CEntity CreateBasicHeightmap(CHeightMapInfo hInfo)
        {
            return AttachModel(new CHeightmapModel(m_node.Name, hInfo));
        }

        #endregion

    }
}
